﻿using System;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Xml;
using System.ComponentModel;

namespace ffmpeg
{
	public class Conversor
	{
		private string conversorNotFoundMessage = "Conversor executable not found!";
		private string QueueXMLNotFoundMessage = "XML file not found!";

		private string QueueFilename { get; set; }
		private string FfmpegPath { get; set; }
		private string FfmpegName { get; set; }
		private string FfmpegArguments { get; set; }
		private string TempFolder { get; set; }
		private string ConvertedFolder { get; set; }
		private string VideoExtension { get; set; }
		private int SimultaneousConversions { get; set; }

		private FileSystemWatcher watcher = null;
		private static object lockXMLCola = new object();

		public Conversor(string queueFilename, string ffmpegPath, string ffmpegName, string fmpegArguments, string tempFolder, string convertedFolder, string videoExtension, int simultaneousConversions)
		{
			if (Directory.GetFiles(ffmpegPath, ffmpegName).Length != 1) throw new FileNotFoundException(conversorNotFoundMessage);
			if (!File.Exists(queueFilename)) throw new FileNotFoundException(QueueXMLNotFoundMessage);

			this.QueueFilename = queueFilename;
			this.FfmpegPath = ffmpegPath;
			this.FfmpegName = ffmpegName;
			this.FfmpegArguments = FfmpegArguments;
			this.TempFolder = tempFolder;
			this.ConvertedFolder = convertedFolder;
			this.VideoExtension = videoExtension;
			this.SimultaneousConversions = simultaneousConversions;
		}

		public void Init()
		{

			Process[] procesos = Process.GetProcessesByName("ffmpeg");
			if (procesos != null && procesos.Length > 0)
			{
				foreach (Process proceso in procesos)
				{
					try
					{
						proceso.Kill();
					}
					catch { }
				}
			}
			bool lanzarCola = false;
			lock (lockXMLCola)
			{
				XmlDocument doc = new XmlDocument();
				doc.Load(this.QueueFilename);
				bool ficheroModificado = false;
				XmlNodeList nodosTransformaciones = doc.DocumentElement.SelectNodes("file[@processing='1']");
				if (nodosTransformaciones != null && nodosTransformaciones.Count > 0)
				{
					foreach (XmlNode nodoTransformacion in nodosTransformaciones) nodoTransformacion.Attributes["processing"].Value = "0";
					ficheroModificado = true;
				}
				XmlNodeList nodosPendientes = doc.DocumentElement.SelectNodes("file[@processing='0']");
				if(ficheroModificado || nodosPendientes.Count > 0) lanzarCola = true;
				if (ficheroModificado) doc.Save(this.QueueFilename);
			}
			FileInfo fiCola = new FileInfo(this.QueueFilename);
			watcher = new FileSystemWatcher(fiCola.DirectoryName);
			watcher.Filter = fiCola.Name;
			watcher.Changed += new FileSystemEventHandler(watcher_Changed);
			watcher.EnableRaisingEvents = true;
			if(lanzarCola) processQueue();
		}

		void processQueue()
		{
			Process[] procesos = Process.GetProcessesByName("ffmpeg");
			if (procesos.Length >= this.SimultaneousConversions) return;
			watcher.Changed -= new FileSystemEventHandler(watcher_Changed);
			watcher.EnableRaisingEvents = false;
			int conversionesPendientes = this.SimultaneousConversions - procesos.Length;
			lock (lockXMLCola)
			{
				XmlDocument doc = new XmlDocument();

				string contenidoFichero = string.Empty;
				while (contenidoFichero == string.Empty)
				{
					try
					{
						contenidoFichero = File.ReadAllText(this.QueueFilename);
					}
					catch 
					{
						Thread.Sleep(1000);
					}
				}
				doc.LoadXml(contenidoFichero);
				bool ficheroModificado = false;
				XmlNodeList nodosTransformaciones = doc.DocumentElement.SelectNodes("file[@processing='0']");
				if (nodosTransformaciones != null && nodosTransformaciones.Count > 0)
				{
					for(int i = 0; i< nodosTransformaciones.Count; i++)
					{
						if(conversionesPendientes > 0)
						{
							nodosTransformaciones[i].Attributes["processing"].Value = "1";
							conversionesPendientes--;
							ConversionTask argumentos = new ConversionTask(nodosTransformaciones[i].InnerText);
							ficheroModificado = true;
							BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
							worker.DoWork += new DoWorkEventHandler(worker_DoWork);
							worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
							worker.RunWorkerAsync(argumentos);
						}
					 }
				}
				if (ficheroModificado) doc.Save(this.QueueFilename);
			}
			watcher.Changed += new FileSystemEventHandler(watcher_Changed);
			watcher.EnableRaisingEvents = true;
		}

		void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if (e.Result == null) return;
			lock(lockXMLCola)
			{
				watcher.Changed -= new FileSystemEventHandler(watcher_Changed);
				try
				{
					if (Resultado<ConversionTask>.CheckType(e.Result))
					{
						Resultado<ConversionTask> devuelto = (Resultado<ConversionTask>)e.Result;
						XmlDocument doc = new XmlDocument();

						string contenidoFichero = string.Empty;
						while (contenidoFichero == string.Empty)
						{
							try
							{
								contenidoFichero = File.ReadAllText(this.QueueFilename);
							}
							catch
							{
								Thread.Sleep(1000);
							}
						}
						doc.LoadXml(contenidoFichero);
						XmlNodeList nodos = doc.DocumentElement.SelectNodes("file");
						bool cambiado = false;
						foreach(XmlNode nodo in nodos)
						{
							if(nodo.InnerText == devuelto.Tarea.SourceFile.FullName)
							{
								doc.DocumentElement.RemoveChild(nodo);
								cambiado = true;
							}
						}
						if (cambiado) doc.Save(this.QueueFilename);

						if (e.Cancelled == false && e.Error == null && devuelto.Correcto == true && File.Exists(devuelto.Tarea.DestinationFileName))
						{
							FileInfo fi = new FileInfo(devuelto.Tarea.DestinationFileName);
							string destinationFilename = this.ConvertedFolder + fi.Name;
							File.Move(devuelto.Tarea.DestinationFileName, destinationFilename);
						}
					}
				}
				catch { }
				watcher.Changed += new FileSystemEventHandler(watcher_Changed);
			}
			processQueue();
		}

		void worker_DoWork(object sender, DoWorkEventArgs e)
		{
			if (e.Argument is ConversionTask)
			{
				try
				{
					ConversionTask argumentos = (ConversionTask)e.Argument;
					if (string.IsNullOrEmpty(this.FfmpegPath) || !File.Exists(argumentos.SourceFile.FullName))
					{
						e.Result = new Resultado<ConversionTask>(false, argumentos);
						return;
					}

					Process cmd = new Process();
					cmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
					cmd.StartInfo.WorkingDirectory = this.FfmpegPath;
					cmd.StartInfo.FileName = this.FfmpegName;
					string destinationFilename = this.TempFolder + argumentos.SourceFile.Name.Substring(0, argumentos.SourceFile.Name.LastIndexOf('.')) + "." + this.VideoExtension;
					cmd.StartInfo.Arguments =  string.Format(this.FfmpegArguments, argumentos.SourceFile.FullName, destinationFilename);
					argumentos.DestinationFileName = destinationFilename;
					cmd.Start();
					cmd.WaitForExit();

					Resultado<ConversionTask> resul = new Resultado<ConversionTask>(true, argumentos);
					e.Result = resul;
				}
				catch 
				{
					e.Result = new Resultado<ConversionTask>(false, (ConversionTask)e.Argument);
				} 
			}
		}

		void watcher_Changed(object sender, FileSystemEventArgs e)
		{
			processQueue();
		}

	}
}
